package com.codeyang.config.shiro;


import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.crazycake.shiro.IRedisManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.Map;

/**
 * 描述: TO-删除标志-DO
 * shiro 自己改造的自动配置类
 * shiro 要集成 redis 存储token
 *
 * @author CodeYang_Site
 * @version 2021/4/20 14:40
 */
@Configuration
// 自动注入对象
@EnableConfigurationProperties(value = {ShiroProperties.class, RedisProperties.class})
public class ShiroAutoConfig {

    /**
     * shiro 的属性配置
     */
    private ShiroProperties shiroProperties;
    /**
     * redis 的属性配置
     */
    private RedisProperties redisProperties;

    /**
     * spring 创建该对象时会自动注入该对象  ref
     * 通过spring的
     *
     * @param shiroProperties
     */
    public ShiroAutoConfig(ShiroProperties shiroProperties, RedisProperties redisProperties) {
        this.shiroProperties = shiroProperties;
        this.redisProperties = redisProperties;
    }

    /**
     * Spring 与 shiro的整合
     *
     * @param defaultSecurityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultSecurityManager defaultSecurityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(defaultSecurityManager);
        // 1 放行与拦截 --- 过滤器链
        Map<String, String> filterChainDefinitionMap = new HashMap<>();
        //yml中配置的
        String[] anonUrls = this.shiroProperties.getAnonUrls();
        // 2key是具体的url value是 过滤器的匹配名称
        for (String anonUrl : anonUrls) {
            filterChainDefinitionMap.put(anonUrl, "anon");
        }
        //3 yml 中配置的
        String[] authcUrls = this.shiroProperties.getAuthcUrls();
        for (String authcUrl : authcUrls) {
            filterChainDefinitionMap.put(authcUrl, "authc");
        }
        // 4 设置 过滤器规则 放入
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        // 5 继续改造 关于shiro 没有登录会默认重定向到一个 页面去,但是前后端分离到项目不需要,我们只需要返回一个json即可
        Map<String, Filter> filters = new HashMap<>();
        LoginFilter loginFilter = new LoginFilter();
        //处理用户未认证访问要认证的地址的跳转问题   默认是跳转到shiroProperties.getLoginUrl()现在改成以json串形式返回
        filters.put("authc", loginFilter);
        shiroFilterFactoryBean.setFilters(filters);
        return shiroFilterFactoryBean;
    }


    /**
     * shiro 需要那些对象才能运行  类似depon 必须要有 这个 UserRealm bena 才可以创建成功
     */
    //1 SecurityManager
    @Bean
    public DefaultSecurityManager securityManager(UserRealm realm, TokenSessionManager sessionManager) {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();

        defaultWebSecurityManager.setRealm(realm);
        // session是怎么管理的
        defaultWebSecurityManager.setSessionManager(sessionManager);

        return defaultWebSecurityManager;
    }


    /**
     * 数据域对象
     * 因为 UserRealm 继承了 AuthorizingRealm 而 认证之后就是密码匹配,所以我们也可以手动的替换和这个密码匹配器 @Bean
     *
     * @param credentialsMatcher
     * @return
     */
    @Bean
    public UserRealm userRealm(CredentialsMatcher credentialsMatcher) {
        UserRealm userRealm = new UserRealm();
        userRealm.setCredentialsMatcher(credentialsMatcher);
        return userRealm;
    }

    /**
     * 接管 shiro的密码匹配器
     *
     * @return
     */
    @Bean
    public CredentialsMatcher credentialsMatcher() {
        // 获得yml中的加密方式  -- 默认本来就是是md5
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(shiroProperties.getHashAlgorithmName());
        // 设置加密次数
        hashedCredentialsMatcher.setHashIterations(shiroProperties.getHashIterations());
        //后面会去走  public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        return hashedCredentialsMatcher;
    }


    /**
     * 接管 shiro的默认(DefaultWebSessionManager)的 session存储方案,
     * shiro默认的存储方案是存储在 内存中 使用线程作为id session对象为value
     * 我们要实现 分布式的部署项目必须要把session|token存储在 redis 远端中 shiro-redis提供了一套方案可以
     * 自动的将登录后的session存储在 redis中 登出后 销毁 redis中的session
     * |||
     * 默认是 MemorySessionDAO 存储在内存中
     *
     * @param redisSessionDAO 依赖spring中 RedisSessionDAO
     * @return
     */
    @Bean
    public TokenSessionManager tokenSessionManager(RedisSessionDAO redisSessionDAO) {
        TokenSessionManager tokenSessionManager = new TokenSessionManager();
        tokenSessionManager.setSessionDAO(redisSessionDAO);
        return tokenSessionManager;
    }

    /**
     * 注入 RedisSessionDAO 也是接管它 但是它需要 关于redis信息配置
     * 如: redis的 地址 端口等
     *
     * @param redisManager 需要依赖和这个
     * @return
     */
    @Bean
    public RedisSessionDAO redisSessionDAO(IRedisManager redisManager) {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager);
        // 通过我们RedisManager 设置set key ，最多存活多久
        // 7 天
        redisSessionDAO.setExpire(7 * 24 * 3600);
        // 设置valu的序列化
//        redisSessionDAO.setValueSerializer(new StringSerializer());
        return redisSessionDAO;
    }


    @Bean
    public IRedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        // 设置 关于redis的连接池的配置
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMinIdle(10);
        jedisPoolConfig.setMaxIdle(20);
        jedisPoolConfig.setMaxTotal(20);
        // 设置jedis配置
        JedisPool pool = new JedisPool(jedisPoolConfig, redisProperties.getHost(), redisProperties.getPort(), 5000,
                redisProperties.getPassword());
        redisManager.setJedisPool(pool);
        return redisManager;
    }


}
